﻿using System;
using System.Threading.Tasks;
using QuailTask = Quail.Task;

namespace Quail.Parallel
{
    /// <summary>
    /// Used by the <see cref="ParallelTestManager{TTestData}"/> class (or a sub class thereof) to run parallel tests.
    /// This class can be used directly or can be userd as a base class.
    /// </summary>
    /// <typeparam name="TShell">The type of test shell to use</typeparam>
    /// <typeparam name="TTestData">The type of test data for each worker to consume</typeparam>
    public class ParallelTestWorker<TShell, TTestData>
        where TShell : ITestShell
    {
        private readonly Action<TShell, TTestData> _runTest;

        /// <summary>
        /// Initializes a new instance of the <see cref="ParallelTestWorker{TShell,TTestData}"/> class.
        /// </summary>
        /// <param name="runTest">An action used by the worker to perform </param>
        public ParallelTestWorker(Action<TShell, TTestData> runTest)
        {
            _runTest = runTest;
        }
        
        /// <summary>
        /// Gets or sets a value indicating whether or not this worker should stop when any one worker throws an exception
        /// </summary>
        public bool StopWhenOtherWorkersAreExceptional { get; set; }
        
        /// <summary>
        /// Starts a worker so it can initialize and being running tests using the data from the work queue.
        /// </summary>
        /// <param name="context">The <see cref="ParallelTestContext{TShell,TTestData}"/> passed in from this worker's <see cref="ParallelTestManager{TShell,TTestData}"/>.</param>
        /// <param name="loopState">The state of the pararral loop running the workers.</param>
        public void Start(ParallelTestContext<TShell, TTestData> context, ParallelLoopState loopState)
        {
            var shell = context.Shell;
            var rootTask = Equals(shell, context.TestManager.RootShell)
                ? context.TestManager.RootTask 
                : shell.ActiveTask;

            var task = rootTask.BeginTask(context.TestManager.ParallelTestName + ".Worker");
            try
            {
                CheckForEarlyTermination(loopState);

                Initialize(shell);

                // loop through queue
                for (;;)
                {
                    CheckForEarlyTermination(loopState);
                    var testData = context.GetNextTestData();
                    if (Equals(testData, default(TTestData)))
                        break;

                    // run test
                    RunTest(shell, testData);
                }

                if (context.TestManager.TestComplete != null)
                    context.TestManager.TestComplete(shell);

                task.Finish();
            }
            catch (Exception e)
            {
                task.Finish(e);
                throw;
            }
        }

        /// <summary>
        /// Used to initalize the worker prior to looping and running tests.  By default, this does nothing.  
        /// If some initialization is nessecary for the worker, a drived class should be created and this method should be overridden
        /// </summary>
        /// <param name="shell">The shell that can be used in this method,</param>
        protected virtual void Initialize(TShell shell)
        {
        }

        /// <summary>
        /// Runs a test using a single item of test data from the work queue.  If 
        /// If deriving from this class, you can override this method.  Otherwise a runTest action must be passed into the public constructor.
        /// </summary>
        /// <param name="shell">The shell to use for this test.</param>
        /// <param name="testData">The data from the work queue to use for this test.</param>
        protected virtual void RunTest(TShell shell, TTestData testData)
        {
            if (_runTest == null)
                throw new NotImplementedException();
            _runTest(shell, testData);
        }

        /// <summary>
        /// Checks to see if the worker should terminate early.
        /// </summary>
        /// <param name="loopState">The current state of the parallel loop running this worker.</param>
        private void CheckForEarlyTermination(ParallelLoopState loopState)
        {
            if (loopState.IsExceptional && StopWhenOtherWorkersAreExceptional)
                throw new Exception("Loop is exceptional and StopWhenOtherWorkersAreExceptional is true");
        }
    }
}
